interface Math {
	clamp(
		v: number,
		min: number[] | number,
		max: number[] | number
	): number
	dist2d(
		x1: number, x2: number, y1: number, y2: number
	): number
	dist(x1: number, x2: number): number
	signed(x: number): boolean
	limitRandom(min: number, max?: number): number
	intRandom(min?: number, max?: number): number
	rads(deg?: number): number
}
Math.clamp = function (
	v: number,
	min: number | number[],
	max: number | number[]
): number {
	const mib = (typeof (min) == "number") ? [min] : min
	const mxb = (typeof (max) == "number") ? [max] : max

	return Math.min(Math.max(
		v, ...mib
	), ...mxb)
}
Math.dist2d = function (
	x1: number,
	x2: number,
	y1: number,
	y2: number
): number {
	return Math.sqrt(
		Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)
	)
}
Math.dist = function (
	x1: number,
	x2: number,
): number {
	return Math.abs(x2 - x1)
}
Math.signed = function (x: number): boolean {
	return x < 0
}
Math.limitRandom = function (
	min: number, max: number = 65536
): number {
	return Math.random() * (max - min) + min;
}
Math.intRandom = function (
	min: number = -32768, max: number = 32767
): number {
	return Math.round(Math.limitRandom(min, max));
}
Math.rads = function (deg?: number): number {
	return deg ? (Math.PI / 180) * deg : 0;
}

interface CustomHTMLElement {
	connectedCallback(): void;
	disconnectedCallback(): void;

	attributeChangedCallback?(name: string, oldValue: string, newValue: string): void;
	adoptedCallback?(): void
}

namespace SDT {
	export interface CopybleObject<T extends CopybleObject<T>> {
		copy(): T;
	}
}

function setNumberBit(v: number, order: number, bit: boolean | null | undefined): number {
	if (!bit) {
		bit = false;
	}
	return v | (+bit << order);
}
function getNumberBit(v: number, order: number): boolean {
	return !!(v | (1 << order));
}

function setBigintBit(v: bigint, order: bigint, bit: boolean | null | undefined): bigint {
	if (!bit) {
		bit = false;
	}
	return v | (BigInt(bit) << order);
}
function getBigintBit(v: bigint, order: bigint): boolean {
	return !!(v | (1n << order));
}

function safeSet<T extends Object>(
	src: T, dest: T, prop: keyof T
): void {
	if (dest[prop] !== undefined) {
		src[prop] = dest[prop]
	}
}

function safeCopyExtend<T extends Object>(
	origin: Required<T>, child: T
): Required<T> {
	let result: Required<T> = {} as Required<T>;
	for (const key in origin) {
		if (Object.prototype.hasOwnProperty.call(origin, key)) {
			if (child[key] !== undefined) {
				result[key] = child[key];
			}
			else if (origin[key] !== undefined) {
				result[key] = origin[key];
			}
		}
	}
	return result;
}
function safeExtend<T extends Object>(
	origin: Required<T>, child: T
): Required<T> {
	for (const key in origin) {
		if (Object.prototype.hasOwnProperty.call(origin, key)) {
			if (child[key] !== undefined) {
				origin[key] = child[key];
			}
		}
	}
	return origin;
}

function gNullIE<T, TR = any>(src: T | undefined, key: keyof T): null | TR {
	if (!src) {
		return null;
	}

	if (!src[key]) {
		return null;
	}
	return src[key] as any;
}
function gNullIEForNumber<T>(src: T | undefined, key: keyof T): number {
	if (!src) {
		return 0;
	}

	if (!src[key]) {
		return 0;
	}
	return src[key] as any;
}
function gNullIEForString<T>(src: T | undefined, key: keyof T): string {
	if (!src) {
		return "";
	}

	if (!src[key]) {
		return "";
	}
	return src[key] as any;
}

function setToReadonly<Obj, T>(o: Obj, p: keyof Obj, v: T): void {
	o[p] = v as any;
}

function charIs(ch: string | undefined, ...chl: string[]): boolean {
	if (ch === undefined) {
		return false;
	}

	for (let i = 0; i < chl.length; i++) {
		if (chl[i] == ch) {
			return true;
		}
	}

	return false;
}

function numberToUInt(v: number): number {
	return Math.max(~~v, 0);
}
function numberToInt(v: number): number {
	return ~~v;
}

namespace SDT {
	export function Color(r: number, g: number, b: number, a: number = 255): BitColor {
		let c = new BitColor();
		c.set(r, g, b, a);
		return c;
	}

	export class BitColor implements CopybleObject<BitColor> {
		protected _Data: number;

		constructor(v?: number | undefined) {
			Object.setPrototypeOf(Object.getPrototypeOf(this) ?? this, null);

			if (v) {
				this.setBinary(v);
			}
		}

		getBinary(): number {
			return this._Data;
		}
		setBinary(v: number): void {
			this._Data = v;
		}

		copy(): BitColor {
			let r = new BitColor();
			r.setBinary(this._Data);
			return r;
		}
		set(red: number, green: number, blue: number, alpha: number = 0xFF): void {
			this._Data = 0;

			this._Data |= red & 0x000000FF;
			this._Data |= (green << 8) & 0x0000FF00;
			this._Data |= (blue << 16) & 0x00FF0000;
			this._Data |= (alpha << 24) & 0xFF000000;
		}

		has(): true {
			return true
		}
		remove(): void {
			this._Data = 0;
		}

		negative(): BitColor {
			let r = new BitColor(~this._Data);
			r.alpha = this.alpha;
			return r;
		}

		isEqual(v: BitColor): boolean {
			return this.alpha === v.alpha &&
				this.red === v.red &&
				this.green === v.green &&
				this.blue === v.blue
		}

		get red(): number {
			return this._Data & 0x000000FF;
		}
		set red(v: number) {
			this._Data = (this._Data & 0xFFFFFF00) | (v & 0xFF);
		}
		get green(): number {
			return (this._Data & 0x0000FF00) >> 8;
		}
		set green(v: number) {
			this._Data = (this._Data & 0xFFFF00FF) | ((v & 0xFF) << 8);
		}
		get blue(): number {
			return (this._Data & 0x00FF0000) >> 16;
		}
		set blue(v: number) {
			this._Data = (this._Data & 0xFF00FFFF) | ((v & 0xFF) << 16);
		}
		get alpha(): number {
			return (this._Data & 0xFF000000) >>> 24;
		}
		set alpha(v: number) {
			this._Data = (this._Data & 0x00FFFFFF) | ((v & 0xFF) << 24);
		}

		static readonly BIT_SIZEOF = 32;

		toString(): string {
			let r = this.red.toString(16);
			if (r.length == 1) {
				r = '0' + r;
			}

			let g = this.green.toString(16);
			if (g.length == 1) {
				g = '0' + g;
			}

			let b = this.blue.toString(16);
			if (b.length == 1) {
				b = '0' + b;
			}

			let a = this.alpha.toString(16);
			if (a.length == 1) {
				a = '0' + a;
			}

			if (a.toLowerCase() === "FF") {
				return `#${r}${g}${b}`;
			}

			return `#${r}${g}${b}${a}`;
		}
	}

	namespace Symbols {
		export const REG_ITEM_LIST: unique symbol = Symbol("SDT.Registry@itemList");
		export const REG_ITEM_COUNT: unique symbol = Symbol("SDT.Registry@itemCount");
		export const REG_DATA_FILTER: unique symbol = Symbol("SDT.Registry@filter");

		export const REG_CACHE_KEYS: unique symbol = Symbol("SDT.Registry@cache.KEYS");
		export const REG_CACHE_ITEMS: unique symbol = Symbol("SDT.Registry@cache.ITEMS");
	}

	export type RegistryValueKey = string | symbol
	export type RegistryValue<T> = T
	export interface RegistryNoteAccess {
		enumerable?: boolean;
		readonly?: boolean;
		deletable?: boolean;
		configurable?: boolean;
	}
	export interface RegistryNote<T> extends RegistryNoteAccess {
		value: RegistryValue<T>
	}

	export type RegistryValueFilter<T> = (value: RegistryValue<T>) => boolean;
	export type RegistryValueIsEqual<T> = (v: RegistryValue<T>, v2: RegistryValue<T>) => boolean;

	export class Registry<T, DefaultKey extends string = string> {
		private [Symbols.REG_ITEM_LIST]: {
			[key: RegistryValueKey]: Required<RegistryNote<T>>
		};
		private [Symbols.REG_ITEM_COUNT]: number;

		private [Symbols.REG_CACHE_KEYS]: RegistryValueKey[] | null;
		private [Symbols.REG_CACHE_ITEMS]: RegistryValue<T>[] | null;

		private [Symbols.REG_DATA_FILTER]: RegistryValueFilter<T>;
		isEqual: RegistryValueIsEqual<T>;

		constructor(filter?: RegistryValueFilter<T>, isEqual?: RegistryValueIsEqual<T>) {
			Object.setPrototypeOf(Object.getPrototypeOf(this) ?? this, null);

			this[Symbols.REG_ITEM_LIST] = {};
			this[Symbols.REG_ITEM_COUNT] = 0;
			if (!filter) {
				filter = (): true => {
					return true;
				}
			}

			this[Symbols.REG_DATA_FILTER] = filter;

			if (!isEqual) {
				isEqual = (v, v2) => {
					return v === v2;
				}
			}

			this.isEqual = isEqual;
			this[Symbols.REG_CACHE_KEYS] = null;
			this[Symbols.REG_CACHE_ITEMS] = null;
		}

		private _Create_Note(base: RegistryNote<T>): Required<RegistryNote<T>> {
			let r = {
				value: base.value,
				...this._Create_Access(base)
			}
			Object.setPrototypeOf(r, null);
			return r;
		}
		private _Create_Access(base: RegistryNoteAccess): Required<RegistryNoteAccess> {
			let r = {
				readonly: base.readonly ?? false,
				configurable: base.configurable ?? true,
				deletable: base.deletable ?? true,
				enumerable: base.enumerable ?? true
			}
			Object.setPrototypeOf(r, null);
			return r;
		}

		write(key: DefaultKey, value: RegistryValue<T>, acces?: RegistryNoteAccess): boolean
		write(key: RegistryValueKey, value: RegistryValue<T>, acces?: RegistryNoteAccess): boolean
		write(key: RegistryValueKey, value: RegistryValue<T>, acces: RegistryNoteAccess = {}): boolean {
			if (!this.registr(key, value, acces)) {
				let r = this.writeValue(key, value);
				if (r) {
					return this.modify(key, acces);
				}
			}

			return false;
		}
		writeValue(key: DefaultKey, value: RegistryValue<T>): boolean
		writeValue(key: RegistryValueKey, value: RegistryValue<T>): boolean
		writeValue(key: RegistryValueKey, value: RegistryValue<T>): boolean {
			if (value !== undefined) {
				if (!this[Symbols.REG_DATA_FILTER](value)) {
					return false;
				}

				if (!this.registrValue(key, value)) {
					return this.replace(key, value)
				}
			}
			return false;
		}

		replace(key: DefaultKey, value: RegistryValue<T>): boolean
		replace(key: RegistryValueKey, value: RegistryValue<T>): boolean
		replace(key: RegistryValueKey, value: RegistryValue<T>): boolean {
			if (value !== undefined) {
				if (!this[Symbols.REG_DATA_FILTER](value)) {
					return false;
				}

				if (this.has(key)) {
					if (!this[Symbols.REG_ITEM_LIST][key]?.readonly) {
						this[Symbols.REG_ITEM_LIST][key]!.value = value;
						return true;
					}
				}
			}
			return false;
		}
		modify(key: DefaultKey, config: RegistryNoteAccess): boolean
		modify(key: RegistryValueKey, config: RegistryNoteAccess): boolean
		modify(key: RegistryValueKey, config: RegistryNoteAccess): boolean {
			let c = this.getConfig(key)

			if (c) {
				if (c.configurable) {
					Object.setPrototypeOf(config, null);
					safeExtend(c, config);
					return true;
				}
			}
			return false;
		}

		static generateKey(): string {
			return crypto.randomUUID();
		}

		registr(key: RegistryValueKey, value: RegistryValue<T>, access: RegistryNoteAccess = {}): boolean {
			if (this.has(key)) {
				return false;
			}

			this[Symbols.REG_ITEM_COUNT]++;
			this[Symbols.REG_ITEM_LIST][key] = this._Create_Note({
				value: value,
				...access
			});

			this[Symbols.REG_CACHE_KEYS] = null;
			this[Symbols.REG_CACHE_ITEMS] = null;

			return true;
		}
		registrValue(key: RegistryValueKey, value: RegistryValue<T>): boolean {
			return this.registr(key, value);
		}

		has(key: DefaultKey): true
		has(key: RegistryValueKey): boolean
		has(key: RegistryValueKey): boolean {
			return Object.hasOwn(this[Symbols.REG_ITEM_LIST], key);
		}

		read(key: DefaultKey): undefined | RegistryValue<T>
		read(key: RegistryValueKey): undefined | RegistryValue<T>
		read(key: RegistryValueKey): undefined | RegistryValue<T> {
			if (this.has(key)) {
				return this[Symbols.REG_ITEM_LIST][key]?.value;
			}
			return undefined;
		}
		getConfig(key: RegistryValueKey): undefined | Required<RegistryNoteAccess> {
			if (this.has(key)) {
				return this._Create_Access(this[Symbols.REG_ITEM_LIST][key]!);
			}
			return undefined;
		}

		remove(key: RegistryValueKey): boolean {
			if (this.has(key)) {
				if (!this.getConfig(key)?.deletable) {
					return false;
				}
				this[Symbols.REG_ITEM_COUNT]--;
				delete this[Symbols.REG_ITEM_LIST][key];
				return true;
			}

			this[Symbols.REG_CACHE_KEYS] = null;
			this[Symbols.REG_CACHE_ITEMS] = null;
			return false;
		}

		append(access: RegistryNoteAccess, ...value: RegistryValue<T>[]): (string | undefined)[] {
			let ra: (string | undefined)[] = [];
			value.forEach(v => {
				const r = Registry.generateKey();

				if (!this.registr(r, v, access)) {
					ra.push(undefined);
				}
				ra.push(r);
			})
			return ra;
		}
		appendValue(...value: RegistryValue<T>[]): (string | undefined)[] {
			return this.append({}, ...value);
		}

		search(v: RegistryValue<T>): RegistryValueKey[] {
			let r: RegistryValueKey[] = [];
			const arr = this.itemKeys();

			arr.forEach(e => {
				const val = this.read(e)!;

				if (this.isEqual(v, val)) {
					r.push(e);
				}
			})
			return r;
		}

		copyReference(key: RegistryValueKey, source: RegistryValueKey, access: RegistryNoteAccess = {}): boolean {
			if (this.has(key)) {
				return false;
			}

			let own = this[Symbols.REG_ITEM_LIST][source];
			if (!own) {
				return false;
			}

			this.registr(key, own.value, {
				configurable: own.configurable && access.configurable,
				deletable: own.deletable && access.deletable,
				enumerable: own.enumerable && access.enumerable,
				readonly: own.readonly || access.readonly
			})

			return true;
		}

		itemKeys(): RegistryValueKey[] {
			let r: RegistryValueKey[] = [];

			if (this[Symbols.REG_CACHE_KEYS]) {
				return this[Symbols.REG_CACHE_KEYS];
			}

			for (const key in this[Symbols.REG_ITEM_LIST]) {
				if (Object.prototype.hasOwnProperty.call(this[Symbols.REG_ITEM_LIST], key)) {
					const element = this[Symbols.REG_ITEM_LIST][key];

					if (element?.enumerable) {
						r.push(key);
					}
				}
			}

			this[Symbols.REG_CACHE_KEYS] = r;
			return r;
		}
		items(): RegistryValue<T>[] {
			let r: RegistryValue<T>[] = [];

			if (this[Symbols.REG_CACHE_ITEMS]) {
				return this[Symbols.REG_CACHE_ITEMS];
			}

			for (const key in this[Symbols.REG_ITEM_LIST]) {
				if (Object.prototype.hasOwnProperty.call(this[Symbols.REG_ITEM_LIST], key)) {
					const element = this[Symbols.REG_ITEM_LIST][key];

					if (element?.enumerable) {
						r.push(element.value);
					}
				}
			}

			this[Symbols.REG_CACHE_ITEMS] = r;
			return r;
		}

		[Symbol.iterator](): Iterator<{
			value: RegistryValue<T>,
			key: RegistryValueKey,
			index: number
		}> & {
			index: number, keys: RegistryValueKey[]
		} {
			const owner = this;

			return {
				index: 0,
				keys: this.itemKeys(),
				next() {
					if (this.index == this.keys.length) {
						return {
							done: true,
							value: undefined
						}
					}

					const key = this.keys[this.index]!;
					let r = owner.read(key)!
					this.index++;

					return {
						value: {
							index: this.index,
							key: key,
							value: r
						}
					}
				}
			}
		}

		get length(): number {
			return this[Symbols.REG_ITEM_COUNT];
		}

		clear(): void {
			this[Symbols.REG_CACHE_ITEMS] = [];
			this[Symbols.REG_CACHE_KEYS] = [];
			this[Symbols.REG_ITEM_COUNT] = 0;
			this[Symbols.REG_ITEM_LIST] = Object.create(null);
		}
	}

	export const SpaceCharsRegistry: Registry<string> = new Registry<string>((v) => {
		return v.length == 1;
	})

	SpaceCharsRegistry.append({
		deletable: false,
		readonly: true,
		configurable: false,
		enumerable: true
	}, ' ', '\t', '\n', '\u200B', '\r', ' ', ' ', ' ');
}

function pickWord(str: string, index: number, options: {
	blackList?: string,
	whiteList?: string,
	nullableReset?: boolean
} = {}): string {
	let iarr = SDT.SpaceCharsRegistry.items();
	let defstr = "";

	iarr.forEach(e => {
		defstr += e;
	})

	const opts = {
		blackList: options.blackList ?? defstr,
		whiteList: options.whiteList ?? "",
		nullableReset: options.nullableReset ?? true
	}

	function checkChar(ch: string | undefined): boolean {
		if (!ch) {
			return false;
		}

		if (charIs(ch, ...opts.blackList)) {
			if (!charIs(ch, ...opts.whiteList)) {
				return false;
			}
		}
		return true;
	}

	if (!checkChar(str[index])) {
		return opts.nullableReset
			? ""
			: str[index] !== undefined
				? str[index]!
				: "";
	}

	let wbegin = index
	for (let it = index - 1; it >= 0; it--) {
		if (!checkChar(str[it])) {
			break;
		}

		wbegin = it;
	}

	let wend = index
	for (let it = index + 1; it < str.length; it++) {
		if (!checkChar(str[it])) {
			break;
		}

		wend = it;
	}

	return str.substring(wbegin, wend + 1);
}

function wait(ms: number): Promise<void> {
	return new Promise<void>((returnFunction) => {
		setTimeout(() => {
			returnFunction();
		}, ms)
	})
}
